#!/bin/bash

########## DEFAULT VALUES ###############################################
# boolean variables are '1' for true or anything else for false.
insecure=0 # better method to ungrab the pointer, but: unix.stackexchange.com/a/40472
dunst=0 # if you don't want this script to accidentally start dunst, better set it to 0
font="-xos4-terminus-medium-r-normal--14-140-72-72-c-80-iso10646-1"
# will stop processes listed in this array when ss activates and continue them after
stopcont=()

# disable as many options as possible.
urxvtopts=( +rv +ss +j -fade 0 +is +ls +vb +sb +si -sl 0 -pe '' +ssc +hold -lsp 0 -letsp 0 -bc -uc -b 0 -w 0 -bl -override-redirect )

########## USER CUSTOMISATION ################
config="$XDG_CONFIG_HOME/termss/${0##*/}.conf"
[ -r "$config" ] && . "$config"
##############################################

usage() {
echo "Please provide the path to an executable script as first argument (either
absolute, or starting with \"./\").

If the first argument is \"screencast\", a gif screencast of the screensaver in
action is produced. This function requires imagemagick (namely the \"import\"
command) and gifsicle. In that case, the second argument should be the path to
the script to execute.

Subsequent arguments are passed to the script.
"
exit 1
}
cleanup() {
[[ "$unpause" == true ]] && dunstctl set-paused false
[ -n "$urxvtpid" ] && kill "$urxvtpid"
[ -n "$cursor_hide_pid" ] && kill "$cursor_hide_pid"
if [[ "$insecure" == 1 ]]; then
    setxkbmap -option
    setxkbmap -option "$oldoptions"
else
    xdotool mousemove "$oldX" "$oldY"
fi
}
trap cleanup EXIT

# sleep as a builtin
for file in /usr/lib/bash/sleep /usr/lib32/bash/sleep /usr/lib64/bash/sleep; do
    [ -r "$file" ] && enable -f "$file" sleep && break
done
# Portable enough?

[[ "$1" == "-h" ]] && usage
[[ "$1" == "screencast" ]] && screencast=1 && shift

# dependencies
for i in "$1" urxvt xdotool xrandr sed xinput; do
    type "$i" >/dev/null || usage
done

# optional dependencies for screencast
if [[ "$screencast" == 1 ]]; then
    for i in import gifsicle; do
        type "$i" >/dev/null || usage
    done
fi

# optional dependencies for insecure=1
[[ "$insecure" == 1 ]] && hash setxkbmap || insecure=false

if [ -z "$charheight" ] || [ -z "$charwidth" ]; then
    # If the user hasn't defined the height & width of 1 char
    # start a hidden terminal sized 1x1 and let xdotool check its geometry
    tmpfile="${TMPDIR:-/tmp}/${0##*/}$RANDOM"
    urxvt -fn "$font" -geometry 1x1 "${urxvtopts[@]}" \
    -iconic -name donotloadanyresources -geometry 1x1 \
    -e sh -c "xdotool getwindowgeometry --shell \$WINDOWID > \"$tmpfile\""
    . "$tmpfile"
    charwidth="${charwidth:-$WIDTH}"
    charheight="${charheight:-$HEIGHT}"
    rm "$tmpfile"
    unset X Y WINDOW WIDTH HEIGHT SCREEN tmpfile
    echo -e "charwidth=\"$charwidth\"\ncharheight=\"$charheight\"" >> "$config"
fi

screenres="$(xrandr|sed -n '/.*\*.*/p')"
screenwidth="${screenres%%x*}"
term_w="$((screenwidth / charwidth))"
((screenwidth%charwidth > 0)) && ((term_w++))
# a hack: just add 1 column if there's a few pixels left.
# not really a problem though, toilet always seems to leave a margin.

screenres="${screenres#*x}"
screenheight="${screenres%% *}"
term_h="$((screenheight / charheight))"
((screenheight%charheight > 0)) && ((term_h++)) 
# a hack: just add 1 row if there's a few pixels left. but not a big problem, because it scrolls up anyway.

######################### ACTION #########################
((${#stopcont[@]} > 0)) && {
    for i in "${stopcont[@]}"; do echo "Stopping $i"; kill -s SIGSTOP $(pidof "$i"); done
}

case $insecure in
    1)
    # save current options to restore later
    oldoptions="$(xprop -root -notype _XKB_RULES_NAMES)"
    oldoptions="${oldoptions##*\", \"}"; oldoptions="${oldoptions//\"/}"
    # ungrab pointer unix.stackexchange.com/a/40472
    setxkbmap -option grab:break_actions
    xdotool key XF86Ungrab
    ;;
    *)
    #this can be scripted in bash with xwininfo etc. please rewrite evtl.
    #~ killtips # shallowsky.com/blog/programming/killing-tooltips.html

    # get mouse location; restored with trap
    eval $(xdotool getmouselocation --shell)
    oldX="$X"; oldY="$Y"
    xdotool mousemove $screenwidth $screenheight
    ;;
esac

unset X Y screenres charwidth charheight fontdata screenwidth screenheight

# hides the pointer/cursor - killed by trap
if type hhpc; then
    hhpc -i0 & cursor_hide_pid=$!
elif type unclutter; then
    unclutter --idle 0 & cursor_hide_pid=$!
fi

unpause=false
# pause dunst notification daemon - resumed by trap
if [[ "$dunst" == "1" ]] && hash dunstctl && [[ "$(dunstctl is-paused)" == "false"* ]]; then
    dunstctl set-paused true
    unpause=true
fi

urxvt -fn "$font" -geometry "${term_w}x$term_h" "${urxvtopts[@]}" -e "$@" & urxvtpid=$!

# starting the screencast
if [[ "$screencast" == "1" ]]; then
    for ((i=0;;i++)); do
        sleep 0.06
        import -window root termss-$(printf '%04d' $i).gif
    done
fi & scrotpid=$!

sleep 0.1 # something might send an EVENT, better wait a moment
# get input of ALL keyboard & pointer events (focused or not)
xinput test-xi2 --root 2>/dev/null | while read line;do
    if [[ "$line" == EVENT*"(Motion)"* ]] || [[ "$line" == EVENT*"(RawKeyPress)"* ]]; then
        break
    fi
done 
#########################
((${#stopcont[@]} > 0)) && {
    for i in "${stopcont[@]}"; do echo "Continuing $i"; kill -s SIGCONT $(pidof "$i"); done
}

# stopping the screencast
if [[ "$screencast" == "1" ]]; then
    kill $scrotpid
    killall import
    gifsicle --optimize=3 --loopcount=forever --delay 10 termss-*.gif > scrot-termss-$(date +%y%m%d%H%M%S).gif
    rm termss-*.gif
fi

exit 0 # also see cleanup()
